/*
 * mp3 decoder of ccplayer
 * 
 * $Id: mp3dec.c 29 2009-05-05 16:31:09Z ybc2084 $
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/socket.h>

/*#include <id3tag.h>*/
#include <mad.h>

#include "param.h"
#include "queue.h"
#include "msg.h"
#include "sock.h"

/**********************************************************
 * declaration
 **********************************************************/
typedef enum _Decstate	Decstate;
typedef struct _MP3Dec	MP3Dec;

/*
 * decoding state
 */
enum _Decstate {
	STOPPED,
	RUNNING,
	PAUSED,

	EXIT,		/* indicate exit mesg recved */
};

/*
 * core wrapper for mp3 deocoding
 */
struct _MP3Dec {
	int				fd;				/* mp3 file descriptor */
	int				eof;			/* end of mp3 file */
    int             async;          /* 0 - sync, 1 - async */
	unsigned char  *inBuf;			/* input buffer */
	int		        inBufSize;		/* input buffer size */
	int				inBufUsed;		/* used input buffer size */
	unsigned char  *outBuf;			/* output buffer */
	int		        outBufSize;		/* output buffer size */
	int				outBufUsed;		/* used output buffer size */

	int				commSock;		/* command socket to controller */
	int				dataSock;		/* data socket to controller */

	Queue		   *decodedQ;		/* store decoded audio */

	Decstate		state;			/* decoding state */
};

/*
 * table driven:
 * map mesg type to according mesg handler.
 */
typedef struct {
	DEC_MSG_TYPE	msgtype;
	void		  (*msghandler)(MP3Dec *pDec, DEC_MSG *pMsg);
} HANDLER_MAP;

static void handleNewFileMesg(MP3Dec *pDec, DEC_MSG *pMsg);
static void handlePauseMesg(MP3Dec *pDec, DEC_MSG *pMsg);
static void handleResumeMesg(MP3Dec *pDec, DEC_MSG *pMsg);
static void handleStartMesg(MP3Dec *pDec, DEC_MSG *pMsg);
static void handleStopMesg(MP3Dec *pDec, DEC_MSG *pMsg);
static void handleExitMesg(MP3Dec *pDec, DEC_MSG *pMsg);
static void handleFinishMesg(MP3Dec *pDec, DEC_MSG *pMsg);
static void handleExceptMesg(MP3Dec *pDec, DEC_MSG *pMsg);
static void handleAudioMesg(MP3Dec *pDec, DEC_MSG *pMsg);

static HANDLER_MAP MsgTypeToHandlerMapTable[] = {
	{DEC_NEWFILE,		handleNewFileMesg},
	{DEC_PAUSE,			handlePauseMesg},
	{DEC_RESUME,		handleResumeMesg},
	{DEC_START,			handleStartMesg},
	{DEC_STOP,			handleStopMesg},
	{DEC_EXIT,			handleExitMesg},
	{DEC_FINISH,		handleFinishMesg},
	{DEC_EXCEPT,		handleExceptMesg},
	{DEC_AUDIO,			handleAudioMesg},
	{DEC_INVALID,		NULL}	/* sentinel */
};

/*
 * input/output buffer size
 */
static const int IN_BUF_SIZE	= 64 * 1024;
static const int OUT_BUF_SIZE	= 64 * 1024;



/*
 * initial command/data socket with controller
 */
static int initCommSock(MP3Dec *pDec);
static int initDataSock(MP3Dec *pDec);

/*
 * handle comm/data mesg
 */
static void handleCommMesg(MP3Dec *pDec);
static void handleDataMesg(MP3Dec *pDec);
static bool readCommMesg(MP3Dec *pDec, DEC_MSG *pMsg);
static bool writeDataMesg(MP3Dec *pDec, DEC_MSG *pMsg);
static void msgDispatcher(MP3Dec *pDec, DEC_MSG *pMsg);

/*
 * libmad is callback-based
 */
static enum mad_flow
inputCallback(void *exData, struct mad_stream *);

static enum mad_flow
outputCallback(void *exData, struct mad_header const *, struct mad_pcm *);

static enum mad_flow 
headerCallback(void *exData, struct mad_header const *);

static enum mad_flow 
filterCallback(void *exData, struct mad_stream const *, struct mad_frame *);

static enum mad_flow 
errorCallback(void *exData, struct mad_stream *, struct mad_frame *);


/**********************************************************
 * definition
 **********************************************************/

/*
 * mesg handler, for command/audio
 */
static void handleNewFileMesg(MP3Dec *pDec, DEC_MSG *pMsg)
{
	int		mp3fd;

	assert(pDec && pMsg && pMsg->type == DEC_NEWFILE);

	mp3fd = open(pMsg->newfile.fpath, O_RDONLY);
	if (-1 == mp3fd) {
		/* logging */
		pDec->state = STOPPED;
		return;
	}

	pDec->fd = mp3fd;
}

static void handlePauseMesg(MP3Dec *pDec, DEC_MSG *pMsg)
{
	assert(pDec && pMsg && pMsg->type == DEC_PAUSE);

	if (pDec->state == RUNNING)
		pDec->state = PAUSED;
}

static void handleResumeMesg(MP3Dec *pDec, DEC_MSG *pMsg)
{
	assert(pDec && pMsg && pMsg->type == DEC_RESUME);

	if (pDec->state == DEC_PAUSE)
		pDec->state = RUNNING;
}

static void handleStartMesg(MP3Dec *pDec, DEC_MSG *pMsg)
{
	assert(pDec && pMsg && pMsg->type == DEC_START);

	if (pDec->state == STOPPED || pDec->state == PAUSED)
		pDec->state = RUNNING;
}

static void handleStopMesg(MP3Dec *pDec, DEC_MSG *pMsg)
{
	assert(pDec && pMsg && pMsg->type == DEC_STOP);

	if (pDec->state == RUNNING || pDec->state == PAUSED)
		pDec->state = STOPPED;
}

static void handleExitMesg(MP3Dec *pDec, DEC_MSG *pMsg)
{
	assert(pDec && pMsg && pMsg->type == DEC_EXIT);

	pDec->state = EXIT;
}

/*
 * following three handler will send mesg to controller
 */
static void handleFinishMesg(MP3Dec *pDec, DEC_MSG *pMsg)
{
	assert(pDec && pMsg && pMsg->type == DEC_FINISH);

	writeDataMesg(pDec, pMsg);
}

static void handleExceptMesg(MP3Dec *pDec, DEC_MSG *pMsg)
{
	assert(pDec && pMsg && pMsg->type == DEC_EXCEPT);

	writeDataMesg(pDec, pMsg);
}

static void handleAudioMesg(MP3Dec *pDec, DEC_MSG *pMsg)
{
	assert(pDec && pMsg && pMsg->type == DEC_AUDIO);

	writeDataMesg(pDec, pMsg);
}


/*
 * decoder's command/mesg connect to controller
 * RETURNS:
 * 		0 if success, -1 otherwise.
 */
static int initCommSock(MP3Dec *pDec)
{
	int		commSock;

    if (!pDec)
        return (-1);

	commSock = connTo(LOOPBACKIP, DECCOMMPORT);
    if (commSock <= 0)
        return (-1);

	pDec->commSock = commSock;

	return (0);
}

/*
 * decoder's data connect to controller
 * RETURNS:
 * 		0 if success, -1 otherwise.
 */
static int initDataSock(MP3Dec *pDec)
{
	int		dataSock;

    if (!pDec)
        return (-1);

	dataSock = connTo(LOOPBACKIP, DECDATAPORT);
    if (dataSock <= 0)
        return (-1);

	pDec->dataSock = dataSock;

	return (0);
}

/*
 * dispatch command mesg according to mesg type
 */
static void handleCommMesg(MP3Dec *pDec)
{
	DEC_MSG		commMsg;

	if (readCommMesg(pDec, &commMsg)) {
		/* 
		 * read command mesg succeed 
		 */
		msgDispatcher(pDec, &commMsg);
	} else {
		/* 
		 * read command mesg failed
		 */
	}
}

static void handleDataMesg(MP3Dec *pDec)
{
	DEC_MSG		*pDataMsg;

	if (pDec->decodedQ->isEmpty(pDec->decodedQ))
		return;

	pDataMsg = pDec->decodedQ->dequeue(pDec->decodedQ);
	if (!pDataMsg)
		return;

	msgDispatcher(pDec, pDataMsg);
}

static bool readCommMesg(MP3Dec *pDec, DEC_MSG *pMsg)
{
	bool		ret = false;
	ssize_t		len = 0;

	assert(pDec && pMsg);

	len = read(pDec->commSock, pMsg, sizeof(DEC_MSG));
	if (len) {
		/* read succeed */
		ret = true;
	} else if (0 == len) {
		/* unavailable, XXX: should log */
	} else if (-1 == len) {
		if (EINTR == errno) {
			/* interrupted by signal, ignore, just log */
		} else {
			/* other error, maybe sever */
		}
	} else {
		/* shouldn't reach here */
	}

	return (ret);
}

static bool writeDataMesg(MP3Dec *pDec, DEC_MSG *pMsg)
{
	size_t	msglen;

	switch (pMsg->type) {
		case DEC_FINISH:
			msglen = sizeof(DEC_FINISH_MSG);
			break;
		case DEC_EXCEPT:
			msglen = sizeof(DEC_EXCEPT_MSG);
			break;
		case DEC_AUDIO:
			msglen = sizeof(DEC_AUDIO_MSG);
			break;
		default:
			msglen = 0;
			break;
	}

	write(pDec->dataSock, pMsg, msglen);

	return (false);
}

static void msgDispatcher(MP3Dec *pDec, DEC_MSG *pMsg)
{
	int			i;
	int			tabLen;

	tabLen = sizeof(MsgTypeToHandlerMapTable) / 
			 sizeof(MsgTypeToHandlerMapTable[0]);
	for (i = 0; i < tabLen; i++) {
		if (MsgTypeToHandlerMapTable[i].msgtype == DEC_INVALID) {
			return;
		} else if (MsgTypeToHandlerMapTable[i].msgtype == pMsg->type) {
			MsgTypeToHandlerMapTable[i].msghandler(pDec, pMsg);
			break;
		}
	}
}

/*
 * try to receive command from controller, and send data to controller,
 * if there's data in queue.
 */
static enum mad_flow iopoll(void *exData)
{
    MP3Dec             *mp3dec;
    int					maxfd;
    fd_set				rfds;
    fd_set				wfds;
    fd_set				efds;
    struct timespec		timeout;
    sigset_t		   *sigmask = NULL;
    int                 rc;

    mp3dec = (MP3Dec *)exData;
    if (!mp3dec)
        return (MAD_FLOW_BREAK);

    /* poll network incoming and try outgoing, if outgoing data ready.
     * return directly if network inactive after timeout expired.
     */
    FD_ZERO(&rfds);
    FD_ZERO(&wfds);
    FD_ZERO(&efds);
    FD_SET(mp3dec->commSock, &rfds);
    FD_SET(mp3dec->dataSock, &wfds);
    maxfd = ((mp3dec->commSock > mp3dec->dataSock) ? 
            (mp3dec->commSock + 1) : (mp3dec->dataSock + 1));
    timeout.tv_sec	= 0;	/* busy poll */
    timeout.tv_nsec	= 0;
    rc = pselect(maxfd, &rfds, &wfds, &efds, &timeout, sigmask);
    if (rc) {

        /* something is ready */

        if (FD_ISSET(mp3dec->commSock, &rfds)) {
            handleCommMesg(mp3dec);
        }

        if (FD_ISSET(mp3dec->dataSock, &wfds)) {
            handleDataMesg(mp3dec);
        }
    } else if (0 == rc) {

        /* just timeout, nothing ready */

    } else if (-1 == rc) {

        /* error, FIXME: should log? */

        return (MAD_FLOW_BREAK);
    } else {
        /* shouldn't reach here, but who knows, just in case */
        /* FIXME: maybe logging here is a good idea */
    }

    return (MAD_FLOW_CONTINUE);
}

/*
 * feed in stream to mad
 */
static enum mad_flow
inputCallback(void *exData, struct mad_stream *stream)
{
	MP3Dec	*pDec;
	int		 len;

    pDec = (MP3Dec *)exData;

	if (!pDec)
		return (MAD_FLOW_BREAK);

	if ((pDec->state == STOPPED) || (pDec->state == PAUSED) || 
        (!pDec->async && pDec->eof))
		return (MAD_FLOW_STOP);

	/*
	 * if there's remaining data in input buffer,
	 * move it to the beginning of input buffer.
	 */
	if (stream->next_frame) {
		int bytesRemain;
		bytesRemain = &pDec->inBuf[pDec->inBufUsed] - stream->next_frame;
		memmove(pDec->inBuf, stream->next_frame, bytesRemain);
		pDec->inBufUsed = bytesRemain;
	}

	/*
	 * read new data in
	 */
	assert(pDec->fd);
	do {
		len = read(pDec->fd, pDec->inBuf + pDec->inBufUsed,
				   pDec->inBufSize - pDec->inBufUsed);
	} while (-1 == len && errno == EINTR);

	if (-1 == len) {
		/*
		 * error
		 */
		return MAD_FLOW_BREAK;
	} else if (0 == len) {
		/*
		 * end of file, padding to ensure guard buffer length
		 */
		pDec->eof = 1;

        if (pDec->inBufSize - pDec->inBufUsed >= MAD_BUFFER_GUARD) {
            memset(&pDec->inBuf[pDec->inBufUsed], 0, MAD_BUFFER_GUARD);
            pDec->inBufUsed += MAD_BUFFER_GUARD;
        }
	} else {
		pDec->inBufUsed += len;
	}

	mad_stream_buffer(stream, pDec->inBuf, pDec->inBufUsed);

	return (MAD_FLOW_CONTINUE);
}


/**
 * called after decoding a frame
 */
static enum mad_flow
outputCallback(void *exData, 
			   struct mad_header const *header, 
			   struct mad_pcm *pcm)
{
	MP3Dec			*pDec;
	DEC_MSG			*pDecAudioMsg;
	
	pDec = (MP3Dec *)exData;

	if (!pDec)
		return (MAD_FLOW_BREAK);
	
	pDecAudioMsg = (DEC_MSG *)malloc(sizeof(DEC_MSG));
	if (!pDecAudioMsg)
		return (MAD_FLOW_BREAK);

	/* set audio mesg */
	pDecAudioMsg->audio.type	= DEC_AUDIO;
	pDecAudioMsg->audio.header	= *header;
	pDecAudioMsg->audio.pcm		= *pcm;

	pDec->decodedQ->enqueue(pDec->decodedQ, pDecAudioMsg);

	if ((pDec->state == STOPPED || pDec->state == PAUSED) || 
        (!pDec->async && pDec->eof) || 
        (pDec->async && pDec->eof && pDec->decodedQ->isEmpty(pDec->decodedQ)))
		return (MAD_FLOW_STOP);

	return (MAD_FLOW_CONTINUE);
}

static enum mad_flow 
headerCallback(void *exData, struct mad_header const *header)
{
	/* prevent compiler from moaning */
	assert(exData);
	header = header;

	return (MAD_FLOW_CONTINUE);
}

/*
 * principly, filter callback should operate on output frame, but here
 * we just utilize filter callback to implement io polling on loopback
 * network device
 */
static enum mad_flow 
filterCallback(void *exData, 
			   struct mad_stream const *stream, 
			   struct mad_frame *frame)
{
    /* not used, prevent compiler from grumbling */
    stream = stream;
    frame  = frame;

    return (iopoll(exData));
}

static enum mad_flow
errorCallback(void *exData,
			  struct mad_stream *stream,
			  struct mad_frame *frame)
{
	/*
	 * XXX: log, then continue
	 */
	return (MAD_FLOW_CONTINUE);
}

/*
 * get audio message, for async mode
 */
typedef enum {
    DUMMY,          /* dummy */
    GETAUDIO,       /* get a audio block */
    GETSTATE,       /* get decoding state */
} MAD_ASYNC_CMD;

/*
 * 'message' contain incoming message when pass in, while this callback
 * return it will contain outgoing message; Samely, 'msglen' is incoming
 * message length, while returning it will be outgoing message's length.
 *
 * NOTE: 'message' should large enough to contain returned DEC_MSG, 
 * there's no any other method to determine this point.
 */
static enum mad_flow
messageCallback(void *exData,
                void *message,
                unsigned int *msglen)
{
    MP3Dec			*pDec;
	DEC_MSG			*omsg;
    MAD_ASYNC_CMD    imsg;

    pDec = (MP3Dec *)exData;
    if (!pDec)
        return (MAD_FLOW_BREAK);

    if (!pDec->async)
        return (MAD_FLOW_CONTINUE);

    if (*msglen != sizeof(MAD_ASYNC_CMD))
        return (MAD_FLOW_IGNORE);

    imsg = *(MAD_ASYNC_CMD *)message;
    switch (imsg) {
        case GETAUDIO:
            /*if (pDec->eof && pDec->decodedQ->isEmpty(pDec->decodedQ))
                return (MAD_FLOW_STOP);*/

            if (!pDec->decodedQ->isEmpty(pDec->decodedQ)) {
                omsg = (DEC_MSG *)pDec->decodedQ->dequeue(pDec->decodedQ);
                *(DEC_MSG *)message = *omsg;
                *msglen = sizeof(DEC_MSG);

                return (MAD_FLOW_CONTINUE);
            }

            return (MAD_FLOW_IGNORE);
            break;
        case GETSTATE:
            if (pDec->eof && pDec->decodedQ->isEmpty(pDec->decodedQ))
                return (MAD_FLOW_STOP);

            return (MAD_FLOW_CONTINUE);
            break;
        case DUMMY:     /* fall through */
        default:
            return (MAD_FLOW_IGNORE);
    }
}

#ifndef	_CCPLAYER_TEST_		/* if defined: for testing */

/* FIXME: when mad work in sync mode, all work must be done in
 * callbacks
 */
int main(int argc, char *argv[])
{
	struct mad_decoder	decoder;
	MP3Dec			   *mp3dec;
	int					rc;

	mp3dec = (MP3Dec *)malloc(sizeof(MP3Dec));
	if (!mp3dec) {
		/* log, and ... */
		return (-1);
	}

	/*
	 * init sockets
	 */
	initCommSock(mp3dec);
	initDataSock(mp3dec);

    /*
     * FIXME: need to init mp3dec
     */


	/*
	 * init mad decoder
	 */
	mad_decoder_init(&decoder, 
					  mp3dec,
					  inputCallback,		/* input callback */
					  headerCallback,		/* header callback */
					  filterCallback,		/* filter callback */
					  outputCallback,		/* output callback */
					  errorCallback,		/* error callback */
					  messageCallback       /* message callback, 
                                               used merely in async mode */
					);
	rc = mad_decoder_finish(&decoder);


	free(mp3dec);
	return (rc);
}

#endif

